class_name Player
extends CharacterBody2D

signal interact

enum states {
	IDLE,
	RUNNING,
	JUMP,
	DOUBLE_JUMP,
	WALL_JUMP,
	FALL,
	LANDING,
	ATTACK,
	DASH,
	ENTERING,
}
var is_first_tick := false
var GROUND_STATES := [
	states.IDLE,
	states.RUNNING,
	states.LANDING,
]

# Movement
var move_vector := Vector2.ZERO
@export var move_accel: float = 2000  # Horizontal acceleration for movement
@export var move_max_speed: float = 300  # Max movement speed
@export var slow_coefficient: float = -40
var direction: int = 1

# Jump
@export var jump_speed: float = 900
@export var max_fall_speed: float = 900
@export var air_jump_num: int = 1
var air_jump_count: int

# Dash
@export var dash_speed: float = 1400
@export var dash_accel: float = 2500
@export var air_dash_num: int = 1
var air_dash_count: int

# Entering world
enum EnteringMode {
	LEFT,
	RIGHT,
} 
var is_entering: bool
var entering_mode: EnteringMode
@export var enter_distance: float = 50
var already_entered_distance: float

@onready var label: Label = $Label
@onready var state_machine: StateMachine = $StateMachine
@onready var jump_request_timer: Timer = $Timers/JumpRequestTimer  # pre-input
@onready var coyote_timer: Timer = $Timers/CoyoteTimer
@onready var wall_jump_timer: Timer = $Timers/WallJumpTimer
@onready var foot_dector: RayCast2D = $Character/FootDector
@onready var hand_dector: RayCast2D = $Character/HandDector
@onready var character: Node2D = $Character
@onready var dash_request_timer: Timer = $Timers/DashRequestTimer
@onready var dash_timer: Timer = $Timers/DashTimer
@onready var dash_freeze_timer: Timer = $Timers/DashFreezeTimer


func move(delta: float):
	move_vector.y = velocity.y
	
	move_vector.x = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")
	if move_vector.x > 0:  # identifies player's direction
		direction = 1
	elif move_vector.x < 0:
		direction = -1
	velocity.x += move_vector.x * delta * move_accel
	velocity.x = clamp(velocity.x, -move_max_speed, move_max_speed)
	if move_vector.x == 0:
		velocity.x = lerp(0.0, velocity.x, pow(2, slow_coefficient * delta))  # 缓慢减速
	
	fall(delta)
	
	move_and_slide()


func fall(delta: float, gravity: float = ProjectSettings.get("physics/2d/default_gravity")) -> void:
	var tmp_gravity := gravity
	var tmp_fall_speed := max_fall_speed
	if is_player_on_wall() and velocity.y > 0:
		tmp_gravity *= 0.2
		tmp_fall_speed *= 0.3
	velocity.y += tmp_gravity * delta
	if velocity.y > tmp_fall_speed:
		velocity.y = tmp_fall_speed


func can_jump() -> bool:
	return is_on_floor() or coyote_timer.time_left > 0

func is_player_on_wall() -> bool:
	return hand_dector.is_colliding() or foot_dector.is_colliding()


func tick_physics(state: states, delta: float) -> void:
	# DEBUG: display current state
	label.text = states.keys()[state_machine.current_state]
	
	# Player's direction
	character.scale.x = direction
	
	Data.player_direction = direction
	
	match state:
		states.IDLE:
			move(delta)
		states.RUNNING:
			move(delta)
		states.JUMP:
			move(delta)
		states.DOUBLE_JUMP:
			move(delta)
		states.WALL_JUMP:
			if wall_jump_timer.time_left < wall_jump_timer.wait_time * 0.7:
				move(delta)
			else:  # 进入强制上升阶段
				velocity.x = direction * move_max_speed * 1.0  # 蹬墙跳时的反作用力
				velocity.y = -jump_speed * 0.85  # 强制上升阶段时的上升速度
				move_and_slide()
		states.FALL:
			move(delta)
		states.LANDING:
			pass
		states.ATTACK:
			pass
		states.DASH:
			velocity.x += direction * dash_accel * delta
			velocity.y = 0
			move_and_slide()
		states.ENTERING:
			if entering_mode == EnteringMode.LEFT or entering_mode == EnteringMode.RIGHT:
				velocity.x = direction * move_max_speed
				already_entered_distance += abs(velocity.x * delta)
			fall(delta)
			move_and_slide()
	
	is_first_tick = false


func get_next_state(state: states) -> 	states:
	if state == states.ENTERING:
		if already_entered_distance > enter_distance:
			is_entering = false
			return states.IDLE
		else:
			return state
	
	# Jump
	var should_jump := can_jump() and jump_request_timer.time_left > 0
	if should_jump:
		return states.JUMP
	
	var should_wall_jump := is_player_on_wall() and jump_request_timer.time_left > 0
	if should_wall_jump:
		return states.WALL_JUMP
	
	if state_machine.current_state not in GROUND_STATES \
		and air_jump_count < air_jump_num \
		and jump_request_timer.time_left > 0:
		return states.DOUBLE_JUMP
	
	# Dash
	var can_dash := dash_request_timer.time_left > 0 and dash_freeze_timer.is_stopped()
	if can_dash and (can_jump() or is_player_on_wall()):
		return states.DASH
	
	if state_machine.current_state not in GROUND_STATES  \
		and air_dash_count < air_dash_num \
		and can_dash:
		air_dash_count += 1
		return states.DASH
	
	# return an appropriate state inside each condition
	match state:
		states.IDLE:
			if velocity.y > 0:
				return states.FALL
			if not abs(velocity.x) < 50:
				return states.RUNNING
		states.RUNNING:
			if velocity.y > 0:
				return states.FALL
			if abs(velocity.x) < 50:
				return states.IDLE
		states.JUMP:
			if velocity.y > 0:
				return states.FALL
		states.DOUBLE_JUMP:
			if velocity.y > 0:
				return states.FALL
		states.WALL_JUMP:
			if wall_jump_timer.is_stopped():
				return states.JUMP
		states.FALL:
			if is_on_floor():
				return states.IDLE
		states.LANDING:
			pass
		states.ATTACK:
			pass
		states.DASH:
			if dash_timer.is_stopped():
				dash_freeze_timer.start()
				return states.FALL
		states.ENTERING:
			if already_entered_distance > enter_distance:
				is_entering = false
				return states.IDLE
	
	return state


func transition_state(from: states, to: states) -> void:
	
	print("%s: [%s] %s ==> %s" % [
		name,
		Engine.get_physics_frames(),
		states.keys()[from] if from != -1 else "<START>",
		states.keys()[to]
	])
	
	# turn off coyote timer when player lands
	# 落地时重置
	if (from not in GROUND_STATES and to in GROUND_STATES) \
		or is_player_on_wall():  # 贴墙时重置
		coyote_timer.stop()
		air_jump_count = 0
		air_dash_count = 0
	
	# Often used to play the animation between states
	match to:
		states.IDLE:
			pass
		states.RUNNING:
			pass
		states.JUMP:
			if from != states.WALL_JUMP:
				velocity.y = -jump_speed
				jump_request_timer.stop()
				coyote_timer.stop()
		states.DOUBLE_JUMP:
			velocity.y = -jump_speed * 0.9
			air_jump_count += 1
			jump_request_timer.stop()
			coyote_timer.stop()
		states.WALL_JUMP:
			velocity.y = -jump_speed * 0.2
			direction *= -1
			wall_jump_timer.start()
			jump_request_timer.stop()
			coyote_timer.stop()
		states.FALL:
			# start coyote timer when player falls from ground
			if from in GROUND_STATES:
				coyote_timer.start()
		states.LANDING:
			pass
		states.ATTACK:
			pass
		states.DASH:
			if is_player_on_wall():
				direction *= -1
			dash_request_timer.stop()
			velocity.x = direction * dash_speed
			dash_timer.start()
		states.ENTERING:
			is_entering = true
			already_entered_distance = 0
			match entering_mode:
				EnteringMode.LEFT:
					direction = -1
				EnteringMode.RIGHT:
					direction = 1
	
	is_first_tick = true


func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("left_click"):
		global_position = get_global_mouse_position()
	if event.is_action_pressed("jump"):
		jump_request_timer.start()
	if event.is_action_pressed("dash"):
		dash_request_timer.start()
	if event.is_action_released("jump"):
		jump_request_timer.stop()
		wall_jump_timer.stop()
		if velocity.y < -jump_speed * 0.2:
			velocity.y = -jump_speed * 0.2
	if event.is_action_pressed("interact"):
		interact.emit()
